/* Copyright (c) 2006, Jan Flora <janflora@diku.dk>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the University of Copenhagen nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
  @author Jan Flora <janflora@diku.dk>
*/

#include <LocalTime.h>

module AsyncAlarmM
{
	provides
	{
		interface AsyncAlarm<time_t> as Alarm[uint8_t timer];
		interface StdControl;
	}
	uses
	{
		interface LocalTime<time_t> as LocalTime;
		interface SymbolChannel<time_t> as SymbolChannel;
		interface Debug;
	}

}
implementation
{
	#define DBG_LEVEL 1
	#include "Debug.h"

	enum {
		NUM_TIMERS = uniqueCount("AsyncAlarm"),
	};
	
	typedef struct {
		bool valid;
		bool armed;
		time_t timeStamp;
	} Timer_t;

	Timer_t virtualTimers[NUM_TIMERS];
	uint8_t timersPending;

	result_t programAlarm(uint8_t timerId, time_t timeStamp);

	command result_t StdControl.init()
	{
		uint8_t i;
		// Invalidate all timer entries.
		for (i = 0; i < NUM_TIMERS; i++) {
			virtualTimers[i].valid = FALSE;
			virtualTimers[i].armed = FALSE;
		}
		timersPending = 0;
		return SUCCESS;
	}

	command result_t StdControl.start()
	{
		return SUCCESS;
	}

	command result_t StdControl.stop()
	{
		return SUCCESS;
	}


	async command result_t Alarm.armCountdown[uint8_t timer](time_t timeout)
	{
		time_t timeStamp = timeout+call LocalTime.getTime();
		programAlarm(timer, timeStamp);
	}
	
	async command result_t Alarm.armAlarmClock[uint8_t timer](time_t timeStamp)
	{
		programAlarm(timer, timeStamp);
	}

	async command result_t Alarm.stop[uint8_t timer]()
	{
		// Find out if a channel has been armed.
		if (virtualTimers[timer].armed) {
			// Channel has been armed. Try to stop it.
			if (SUCCESS == call SymbolChannel.stop(timer)) {
				virtualTimers[timer].armed = FALSE;
			} else {
				// Stopping of channel failed.
				return FAIL;
			}
		}
		// Invalidate entry.
		virtualTimers[timer].valid = FALSE;
		return SUCCESS;
	}
	
	async event void SymbolChannel.fired(uint8_t id)
	{
		result_t armed;
		bool nextSet = FALSE;
		uint32_t nextDeadline;
		uint8_t nextEntry;
		uint8_t i;
		
		// Free the timer.
		virtualTimers[id].valid = FALSE;
		virtualTimers[id].armed = FALSE;
		
		// Signal the right alarm signal.
		signal Alarm.alarm[id]();
		
		// Exit if no timers are pending.
		if (!timersPending) return;
		
		// Find next timer deadline.
		for (i = 0; i < NUM_TIMERS; i++) {
			if (virtualTimers[i].valid && !(virtualTimers[i].armed)) {
				if (!nextSet) {
					nextEntry = i;
					nextDeadline = virtualTimers[i].timeStamp;
					nextSet = TRUE;
				} else {
					if (call LocalTime.compare(TIME_LT, virtualTimers[i].timeStamp, nextDeadline)) {
						// Current entry is prior to existing.
						nextEntry = i;
						nextDeadline = virtualTimers[i].timeStamp;
					}
				}
			}
		}
		
		// Program channel if any timer was pending.
		if (nextSet) {
			// Try to arm channel.
			armed = call SymbolChannel.arm(nextEntry, nextDeadline);
			if (armed == SUCCESS) {
				virtualTimers[nextEntry].armed = TRUE;
				timersPending--;
			} else {
				// This should never happen.
			}
		}
	}

	result_t programAlarm(uint8_t timerId, time_t timeStamp)
	{
		uint8_t i;
		result_t armed;
		bool replace = FALSE;
		bool replaceEntrySet = FALSE;
		uint8_t replaceEntry;
		time_t replaceTimeStamp;

		// Start by enqueuing the entry.
		virtualTimers[timerId].timeStamp = timeStamp;
		virtualTimers[timerId].valid = TRUE;

		// Try to arm channel.
		armed = call SymbolChannel.arm(timerId, timeStamp);
		if (armed == SUCCESS) {
			// We have a free channel. All is good.
			virtualTimers[timerId].armed = TRUE;
			return SUCCESS;
		}
		
		// We have no free channels. Find out, if this alarm should
		// fire prior to one of those that are currently armed.
		for (i = 0; i < NUM_TIMERS; i++) {
			if (virtualTimers[i].valid && virtualTimers[i].armed) {
				if (!replace && call LocalTime.compare(TIME_LT, timeStamp, virtualTimers[i].timeStamp)) {
					replace = TRUE;
				}
				if (!replaceEntrySet) {
					replaceTimeStamp = virtualTimers[i].timeStamp;
					replaceEntry = i;
					replaceEntrySet = TRUE;
				} else {
					if (call LocalTime.compare(TIME_GT, virtualTimers[i].timeStamp, replaceTimeStamp)) {
						// Current entry is prior to existing.
						replaceEntry = i;
						replaceTimeStamp = virtualTimers[i].timeStamp;
					}
				}
			}
		}
		if (replace) {
			// Disarm the entry to be replaced. It will be queued instead.
			if (SUCCESS == call SymbolChannel.stop(replaceEntry)) {
				virtualTimers[replaceEntry].armed = FALSE;
			} else {
				// This shouldn't happen.
				return FAIL;
			}
			
			// Try to arm channel.
			armed = call SymbolChannel.arm(timerId, timeStamp);
			if (armed == SUCCESS) {
				// We have a free channel. All is good.
				virtualTimers[timerId].armed = TRUE;
				virtualTimers[timerId].timeStamp = timeStamp;
				return SUCCESS;
			} else {
				// This should never happen.
				return FAIL;
			}
		} else {
			// No need to replace. Current timer is queued.
			timersPending++;
		}
		return SUCCESS;
	}

	default async event result_t Alarm.alarm[uint8_t timer]()
	{
		DBG_STR("WARNING: AsyncTimer, Timer fired but not connected:",1);
		DBG_INT(timer,1);
		return SUCCESS;
	}
}
